﻿// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version>$Revision$</version>
// </file>

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;

namespace ICSharpCode.TextEditor
{
	/// <summary>
	/// This class views the line numbers and folding markers.
	/// </summary>
	public class FoldMargin : AbstractMargin
	{
		int selectedFoldLine = -1;

        int foldMarkStyle = 0; //0 默认 Visual Studio样式 1: PyCharm 样式  2: 圆形

        FoldingManager foldingManager = null;

        public override Size Size {
			get {
				return new Size((int)(textArea.TextView.FontHeight),
				                -1);
			}
		}
		
		public override bool IsVisible {
			get {
				return textArea.TextEditorProperties.EnableFolding;
			}
		}
		
		public FoldMargin(TextArea textArea) : base(textArea)
		{
            foldingManager = textArea.Document.FoldingManager;
        }

        public override void Paint(Graphics g, Rectangle rect)
        {
            if (rect.Width <= 0 || rect.Height <= 0)
            {
                return;
            }

            foldMarkStyle = TextEditorProperties.FoldMarkStyle; // textEditorProperties.FoldMarkStyle;

            HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers");
            HighlightColor foldLinePainterColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");

            Brush fillBrush = textArea.Enabled ? BrushRegistry.GetBrush(lineNumberPainterColor.BackgroundColor) : textArea.TextView.UnenableBackgroundBrush;

            if (!textArea.Enabled)
            {
                g.FillRectangle(fillBrush, rect);
            }

            Pen foldLinePen = new Pen(foldLinePainterColor.Color);// { DashStyle = System.Drawing.Drawing2D.DashStyle.Dash };

            List<FoldLine> drawFoldinglines = new List<FoldLine>();

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / textArea.TextView.FontHeight + 1; ++y)
            {
                Rectangle markerRectangle = new Rectangle(DrawingPosition.X,
                                                          DrawingPosition.Top + y * textArea.TextView.FontHeight - textArea.TextView.VisibleLineDrawingRemainder,
                                                          DrawingPosition.Width,
                                                          textArea.TextView.FontHeight);

                if (rect.IntersectsWith(markerRectangle))
                {
                    // draw dotted separator line
                    //if (textArea.Document.TextEditorProperties.ShowLineNumbers)
                    //{
                    //    if (!textArea.Enabled)
                    //        g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder), markerRectangle);
                    //}                    

                    bool isOpened = false;
                    bool isDrawFoldMark = false;
                    
                    int currentLine = textArea.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + y);
                    if (currentLine < textArea.Document.TotalNumberOfLines)
                    {
                        PaintFoldMarker(g, currentLine, markerRectangle,out isDrawFoldMark,out isOpened);
                    }

                    if (foldMarkStyle >= 3 && isDrawFoldMark)
                    {
                        PaintFoldLine(g, markerRectangle, markerRectangle.Bottom, foldLinePen, isOpened);
                        drawFoldinglines.Add(new FoldLine(markerRectangle.Bottom, isOpened));
                    }

                }
            }

            foldingManager.UpdateFoldingLines(drawFoldinglines);

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.Default;

        }
		
		bool SelectedFoldingFrom(List<FoldMarker> list)
		{
			if (list != null) {
				for (int i = 0; i < list.Count; ++i) {
					if (this.selectedFoldLine == list[i].StartLine) {
						return true;
					}
				}
			}
			return false;
		}

        void PaintFoldLine(Graphics g, Rectangle rectangle, int position, Pen foldLinePen, bool isOpened)
        {
            if (!isOpened)
                g.DrawLine(foldLinePen, new Point(rectangle.X + 2, position), new Point(rectangle.Right, position));
        }

        void PaintFoldMarker(Graphics g, int lineNumber, Rectangle drawingRectangle,out bool isDrawFoldMark,out bool isOpened)
        {
            HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
            HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");

            List<FoldMarker> foldingsWithStart = textArea.Document.FoldingManager.GetFoldingsWithStart(lineNumber);
            List<FoldMarker> foldingsBetween = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(lineNumber);
            List<FoldMarker> foldingsWithEnd = textArea.Document.FoldingManager.GetFoldingsWithEnd(lineNumber);

            bool isFoldStart = foldingsWithStart.Count > 0;
            bool isBetween = foldingsBetween.Count > 0;
            bool isFoldEnd = foldingsWithEnd.Count > 0;

            bool isStartSelected = SelectedFoldingFrom(foldingsWithStart);
            bool isBetweenSelected = SelectedFoldingFrom(foldingsBetween);
            bool isEndSelected = SelectedFoldingFrom(foldingsWithEnd);

            isOpened = true;
            isDrawFoldMark = false;

            int foldMarkerSize = (int)Math.Round(textArea.TextView.FontHeight * 0.57f);
            foldMarkerSize -= (foldMarkerSize) % 2;
            int foldMarkerYPos = drawingRectangle.Y + (int)((drawingRectangle.Height - foldMarkerSize) / 2);
            int xPos = drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2 + foldMarkerSize / 2;


            if (isFoldStart)
            {
                bool isVisible = true;
                bool moreLinedOpenFold = false;
                foreach (FoldMarker foldMarker in foldingsWithStart)
                {
                    if (foldMarker.IsFolded)
                    {
                        isVisible = false;
                    }
                    //else
                    //{
                    //    moreLinedOpenFold = foldMarker.EndLine > foldMarker.StartLine;
                    //}
                    moreLinedOpenFold = foldMarker.EndLine > foldMarker.StartLine;
                }

                bool isFoldEndFromUpperFold = false;
                foreach (FoldMarker foldMarker in foldingsWithEnd)
                {
                    if (foldMarker.EndLine > foldMarker.StartLine && !foldMarker.IsFolded)
                    {
                        isFoldEndFromUpperFold = true;
                    }
                }

                //DrawFoldMarker(g, new RectangleF(drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2,
                //                                 foldMarkerYPos,
                //                                 foldMarkerSize,
                //                                 foldMarkerSize),
                //               isVisible,
                //               isStartSelected
                //              );

                DrawFoldMarker(g, new RectangleF(drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2,
                                                 foldMarkerYPos,
                                                 foldMarkerSize,
                                                 foldMarkerSize),
                               isVisible,
                               (isStartSelected || (isBetweenSelected && isBetween && (isFoldEndFromUpperFold || moreLinedOpenFold)))
                              );

                isOpened = isVisible;
                isDrawFoldMark = true;

                if (foldMarkStyle >= 3)
                    return;

                // draw line above fold marker
                if (isBetween || isFoldEndFromUpperFold)
                {
                    g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
                               xPos,
                               drawingRectangle.Top,
                               xPos,
                               foldMarkerYPos - 1);
                }

                // draw line below fold marker
                if (isBetween || moreLinedOpenFold)
                {
                    g.DrawLine(BrushRegistry.GetPen(isEndSelected || (isStartSelected && isVisible) || isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
                               xPos,
                               foldMarkerYPos + foldMarkerSize + 1,
                               xPos,
                               drawingRectangle.Bottom);
                }                

            }
            else
            {
                if (foldMarkStyle >= 3)
                    return;

                if (isFoldEnd)
                {
                    int midy = drawingRectangle.Top + drawingRectangle.Height / 2;

                    // draw fold end marker
                    //g.DrawLine(BrushRegistry.GetPen(isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
                    //           xPos,
                    //           midy,
                    //           xPos + foldMarkerSize / 2,
                    //           midy);

                    g.DrawLine(BrushRegistry.GetPen((isEndSelected || isStartSelected || (isBetweenSelected && isBetween)) ? selectedFoldLine.Color : foldLineColor.Color),
                               xPos,
                               midy,
                               xPos + foldMarkerSize / 2,
                               midy);

                    // draw line above fold end marker
                    // must be drawn after fold marker because it might have a different color than the fold marker
                    g.DrawLine(BrushRegistry.GetPen(isBetweenSelected || isEndSelected ? selectedFoldLine.Color : foldLineColor.Color),
                               xPos,
                               drawingRectangle.Top,
                               xPos,
                               midy);

                    // draw line below fold end marker
                    if (isBetween)
                    {
                        g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
                                   xPos,
                                   midy + 1,
                                   xPos,
                                   drawingRectangle.Bottom);
                    }
                }
                else if (isBetween)
                {
                    // just draw the line :)
                    g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color),
                               xPos,
                               drawingRectangle.Top,
                               xPos,
                               drawingRectangle.Bottom);
                }
            }
        }
		
		public override void HandleMouseMove(Point mousepos, MouseButtons mouseButtons)
		{
			bool  showFolding  = textArea.Document.TextEditorProperties.EnableFolding;
			int   physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
			int   realline     = textArea.Document.GetFirstLogicalLine(physicalLine);
			
			if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
				return;
			}
			
			List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
			int oldSelection = selectedFoldLine;
			if (foldMarkers.Count > 0) {
				selectedFoldLine = realline;
			} else {
				selectedFoldLine = -1;
			}
			if (oldSelection != selectedFoldLine) {
				textArea.Refresh(this);
			}
		}
		
		public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons)
		{
			bool  showFolding  = textArea.Document.TextEditorProperties.EnableFolding;
			int   physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight);
			int   realline     = textArea.Document.GetFirstLogicalLine(physicalLine);

            if (!textArea.TextEditorProperties.EnableFolding)
                return;

            if (!textArea.MotherTextAreaControl.Enabled)
                return;

			// focus the textarea if the user clicks on the line number view
			textArea.Focus();
			
			if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) {
				return;
			}
			
			List<FoldMarker> foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline);
            foreach (FoldMarker fm in foldMarkers)
            {
                fm.IsFolded = !fm.IsFolded;  
            }
            //for (int i = 0; i < foldMarkers.Count; i++)
            //{
            //    var fm = foldMarkers[i];
            //    fm.IsFolded = !fm.IsFolded;
            //}

            textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty);

            //textArea.Document.GuidelinesManager.Update(textArea);
            //textArea.Document.FoldingManager.UpdateFoldings();

        }
		
		public override void HandleMouseLeave(EventArgs e)
		{
			if (selectedFoldLine != -1) {
				selectedFoldLine = -1;
				textArea.Refresh(this);
			}
		}

        #region Drawing functions

        void DrawFoldMarker(Graphics g, RectangleF rectangle, bool isOpened, bool isSelected)
        {
            HighlightColor foldMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldMarker");
            HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine");
            HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine");

            Rectangle intRect = new Rectangle((int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height);

            //if (textArea.backgroundImage == null)
            //    g.FillRectangle((textArea.Enabled ? BrushRegistry.GetBrush(foldMarkerColor.BackgroundColor) : textArea.TextView.UnenableBackgroundBrush), intRect);

            if (foldMarkStyle == 0)
            {
                g.DrawRectangle(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldLineColor.Color), intRect);

                int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1;
                int mid = intRect.Height / 2 + intRect.Height % 2;

                // draw minus
                g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                           rectangle.X + space,
                           rectangle.Y + mid,
                           rectangle.X + rectangle.Width - space,
                           rectangle.Y + mid);

                // draw plus
                if (!isOpened)
                {
                    g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                               rectangle.X + mid,
                               rectangle.Y + space,
                               rectangle.X + mid,
                               rectangle.Y + rectangle.Height - space);
                }
            }
            else if (foldMarkStyle == 1)
            {
                Pen drawPen = BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color);

                g.DrawLine(drawPen, new PointF(rectangle.X + rectangle.Width * 1 / 4, rectangle.Y + rectangle.Height / 2),
                    new PointF(rectangle.X + rectangle.Width * 3 / 4, rectangle.Y + rectangle.Height / 2));

                System.Drawing.Drawing2D.GraphicsPath drawPath = new System.Drawing.Drawing2D.GraphicsPath();

                if (!isOpened)
                {
                    g.DrawLine(drawPen, new PointF(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height * 1 / 4),
                        new PointF(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height * 3 / 4));

                    drawPath.AddLines(new PointF[] {
                         new PointF(rectangle.X,rectangle.Y),
                         new PointF(rectangle.X + rectangle.Width * 3 / 4,rectangle.Y),
                         new PointF(rectangle.Right,rectangle.Y + rectangle.Height / 2),
                         new PointF(rectangle.X + rectangle.Width * 3 / 4,rectangle.Bottom),
                         new PointF(rectangle.X,rectangle.Bottom)
                    });
                }
                else
                {
                    drawPath.AddLines(new PointF[] {
                         new PointF(rectangle.X,rectangle.Y),
                         new PointF(rectangle.Right,rectangle.Y),
                         new PointF(rectangle.Right,rectangle.Y + rectangle.Height * 3 / 4),
                         new PointF(rectangle.X + rectangle.Width / 2,rectangle.Bottom),
                         new PointF(rectangle.X,rectangle.Y + rectangle.Height * 3 / 4)
                    });
                }

                drawPath.CloseFigure();

                g.DrawPath(drawPen, drawPath);

                drawPath.Dispose();

            }
            else if (foldMarkStyle == 2)
            {
                g.DrawEllipse(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldLineColor.Color), intRect);

                int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1;
                int mid = intRect.Height / 2 + intRect.Height % 2;

                // draw minus
                g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                           rectangle.X + space,
                           rectangle.Y + mid,
                           rectangle.X + rectangle.Width - space,
                           rectangle.Y + mid);

                // draw plus
                if (!isOpened)
                {
                    g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                               rectangle.X + mid,
                               rectangle.Y + space,
                               rectangle.X + mid,
                               rectangle.Y + rectangle.Height - space);
                }
            }
            else if (foldMarkStyle == 3)
            {
                RectangleF drawRectangle = new RectangleF(rectangle.X + 1, rectangle.Y + 1, rectangle.Width - 2, rectangle.Height - 2);
                Brush fillBrush = BrushRegistry.GetBrush(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color);
                using (System.Drawing.Drawing2D.GraphicsPath drawPath =
                    new System.Drawing.Drawing2D.GraphicsPath())
                {
                    if (!isOpened)
                    {
                        drawPath.AddLines(new PointF[] {
                             new PointF(drawRectangle.X,drawRectangle.Y),
                             new PointF(drawRectangle.X + drawRectangle.Width-2,drawRectangle.Y + drawRectangle.Height / 2),
                             new PointF(drawRectangle.X,drawRectangle.Bottom)
                        });
                    }
                    else
                    {
                        drawPath.AddLines(new PointF[] {
                             new PointF(drawRectangle.X,drawRectangle.Y),
                             new PointF(drawRectangle.X + drawRectangle.Width,drawRectangle.Y),
                             new PointF(drawRectangle.X + drawRectangle.Width / 2,drawRectangle.Bottom-2)
                        });
                    }
                    drawPath.CloseFigure();
                    g.FillPath(fillBrush, drawPath);
                }
            }
            else if (foldMarkStyle == 4)
            {
                int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1;
                int mid = intRect.Height / 2 + intRect.Height % 2;

                // draw minus
                g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                           rectangle.X + space,
                           rectangle.Y + mid,
                           rectangle.X + rectangle.Width - space,
                           rectangle.Y + mid);

                // draw plus
                if (!isOpened)
                {
                    g.DrawLine(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldMarkerColor.Color),
                               rectangle.X + mid,
                               rectangle.Y + space,
                               rectangle.X + mid,
                               rectangle.Y + rectangle.Height - space);
                }
            }
            
        }

		#endregion
	}
}
